/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.IOException;



/******************************************************************************/
/**
 * Handles information related to optional features.
 * <p>
 *
 * Between them, the converters support a variety of different features.  It
 * is moderately convenient to write converters largely as though we were
 * ignorant of this fact, and then congregate in one place all the stuff which
 * tailors a specific converter to the particular list of features it supports.
 * <p>

 * In particular, the converters between them support a variety of different
 * outputs -- scripture text, footnotes, table of contents, etc -- but not
 * all individual converters handle <i>all</i> of these, and where a given
 * feature is supported, one converter might, for instance, expect the
 * associated data always to be written to a separate file, while another
 * might expect it to be written in-line in the scripture file output.
 * <p>

 * This places some constraints on the type of configuration information we
 * expect and / or are prepared to accept.  For instance, we may be able to
 * determine, by reference to all the converters as a collection, the fact
 * that a given parameter must always have the value X, Y or Z; but a
 * <i>given</i> converter may accept only X and Y, say.  Moreover, there
 * may be some benefit, where a particular converter can only accept one
 * particular value for an input parameter, in proceeding as though the
 * user had supplied that value, regardless of what value they
 * <i>actually</i> supplied.
 * <p>
 *
 * Using the present class also provides a convenient way of documenting
 * the features supported by a particular converter, since so long as we
 * co-locate all the calls to methods in the present class, the list of
 * calls gives a fair indication of what the converter supports.
 * <p>
 *
 * As will be apparent from this discussion, the main parameters available
 * for tailoring here are the ones which relate to file output -- ie the
 * ones which determine whether a particular type of output is available at
 * all, and if so (for instance) whether it should appear in a single file
 * or in multiple files.
 * <p>
 *
 * The bulk of the code below is split up into blocks, each of which
 * addresses a particular area of customisation, and each of which
 * lets you specify the option list supported by the parser/convert,
 * along, optionally, with a default value to be used if none is available
 * from the configuration input.  The caller must invoke <i>all</i> of
 * these blocks so as to indicate what options it supports.  Then at
 * the end of processing, it calls </i>validateConfigurationParameters</i>
 * which checks things, sets up defaults, etc.
 * <p>
 *
 * There are two exceptions to the general rule -- the code which deals
 * with book assembly parameters, and the code which deals with TOC.
 * <p>
 *
 * The former of these lets you indicate whether a particular parser
 * or converter is happy to be dictated to by the book assembly
 * information in respect of TOC and / or scripture files and / or
 * peripheral files and / or notes.  This really limits what common
 * code will do on behalf of the individual converter.  The upshot
 * is that you can supply a single book assembly to all converters
 * where appropriate, and they can pick and choose which bits they
 * will process.
 * <p>
 *
 * At the time of writing, TOC processing is a separate step for
 * all converters, and they write the TOC to a separate file.
 * The block of code below related to TOC lets you indicate whether
 * a particular converter supports TOC.  Note that if you are
 * prepared to have book assembly dictate to you that there should
 * be a TOC, then you must have signed up to create one in the
 * converter.
 * <p>
 *
 * NOTE: This almost certainly fires across the bows of existing code in some
 * places -- ie we have more than one place trying to control the parameters.
 * I _hope_ in these cases that the two areas are both trying to achieve the
 * same thing: it's a bit difficult to rationalise all the code now without
 * running the risk of producing something which is not backward compatible.
 *
 */
/******************************************************************************/

public class FeatureManager
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                Public                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Singleton.

      @return Singleton instance.
   */

  public static FeatureManager instance ()
  {
    if (null == m_Instance) m_Instance = new FeatureManager();
    return m_Instance;
  }


  /****************************************************************************/
  /**
      Carries out general validation.
   
      @param issueWarnings Controls whether warnings are issued.  If negative,
             they are not issues, even if 

      @throws ConverterException Parameters are invalid.
   */

  public void validateConfigurationParameters (int issueWarnings)
          throws IOException, ConverterException
  {
    try
    {
      LogManager.instance().setReportWarnings(issueWarnings);
      validateBookAssemblyParameters();
      validateInputParameters();
      validateOutputEndnotesDispositionParameters();
      validateOutputFootnotesDispositionParameters();
      validateOutputIntroductionDispositionParameters();
      validateOutputCrossReferencesDispositionParameters();
      validateMiscellaneousParameters();
      validateOutputOverallDispositionParameters();
      validateOutputParameters();
      validateOutputScriptureTextDispositionParameters();
      validateTocParameters();
    }
    finally
    {
       LogManager.instance().setReportWarnings(-issueWarnings);
    }
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                        Book-assembly-related                           **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /* The purpose of this section is really to enable us to have a single book
   * assembly file which includes all relevant scripture and peripherals
   * details, and then to be able to ignore the peripheral details on those
   * converters which do not support peripherals (thus avoiding the need to
   * tailor the book assembly file to the particular application).
   */

  /****************************************************************************/
  /**
   * Record an overall indication that we do (or do not) accept directives
   * from the book assembly data.  (Saves calling the individual set
   * methods where all will have the same setting.)
   *
   * @param yesNo True if we do.
   */

  public void setAcceptDirectivesFromBookAssembly (boolean yesNo)
  {
    setAcceptNotesDirectivesFromBookAssembly(yesNo);
    setAcceptPeripheralsDirectivesFromBookAssembly(yesNo);
    setAcceptScriptureDirectivesFromBookAssembly(yesNo);
    setAcceptTocDirectivesFromBookAssembly(yesNo);
  }


  /****************************************************************************/
  /**
   * Returns an indication of whether or not we accept notes directives from
   * the book assembly data.
   *
   * @return True if we do.
   */

  public boolean getAcceptNotesDirectivesFromBookAssembly () { return m_AcceptNotesDirectivesFromBookAssembly; }


  /****************************************************************************/
  /**
   * Records an indication of whether we accept notes directives from the
   * book assembly data.
   *
   * @param yesNo True if we do.
   */

  public void setAcceptNotesDirectivesFromBookAssembly (boolean yesNo)
  {
    m_AcceptNotesDirectivesFromBookAssemblyRecorded = true;
    m_AcceptNotesDirectivesFromBookAssembly = yesNo;
  }


  /****************************************************************************/
  /**
   * Returns an indication of whether or not we accept peripherals directives
   * from the book assembly data.
   *
   * @return True if we do.
   */

  public boolean getAcceptPeripheralsDirectivesFromBookAssembly () { return m_AcceptPeripheralsDirectivesFromBookAssembly; }


  /****************************************************************************/
  /**
   * Records an indication of whether we accept peripherals directives from the
   * book assembly data.
   *
   * @param yesNo True if we do.
   */

  public void setAcceptPeripheralsDirectivesFromBookAssembly (boolean yesNo)
  {
    m_AcceptPeripheralsDirectivesFromBookAssemblyRecorded = true;
    m_AcceptPeripheralsDirectivesFromBookAssembly = yesNo;
  }


  /****************************************************************************/
  /**
   * Returns an indication of whether or not we accept toc directives
   * from the book assembly data.
   *
   * @return True if we do.
   */

  public boolean getAcceptTocDirectivesFromBookAssembly () { return m_AcceptTocDirectivesFromBookAssembly; }


  /****************************************************************************/
  /**
   * Records an indication of whether we accept toc directives from the
   * book assembly data.
   *
   * @param yesNo True if we do.
   */

  public void setAcceptTocDirectivesFromBookAssembly (boolean yesNo)
  {
    m_AcceptTocDirectivesFromBookAssemblyRecorded = true;
    m_AcceptTocDirectivesFromBookAssembly = yesNo;
  }


  /****************************************************************************/
  /**
   * Returns an indication of whether or not we accept scripture directives
   * from the book assembly data.
   *
   * @return True if we do.
   */

  public boolean getAcceptScriptureDirectivesFromBookAssembly () { return m_AcceptScriptureDirectivesFromBookAssembly; }


  /****************************************************************************/
  /**
   * Records an indication of whether we accept scripture directives from the
   * book assembly data.
   *
   * @param yesNo True if we do.
   */

  public void setAcceptScriptureDirectivesFromBookAssembly (boolean yesNo)
  {
    m_AcceptScriptureDirectivesFromBookAssemblyRecorded = true;
    m_AcceptScriptureDirectivesFromBookAssembly = yesNo;
  }


  /****************************************************************************/
  /*
     Checks that the options actually selected are mutually compatible, and
     carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateBookAssemblyParameters() throws ConverterException
  {
    if (!m_AcceptNotesDirectivesFromBookAssemblyRecorded)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated whether it acceptes notes directives from the book assembly.");

    if (!m_AcceptPeripheralsDirectivesFromBookAssemblyRecorded)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated whether it acceptes peripherals directives from the book assembly.");

    if (!m_AcceptScriptureDirectivesFromBookAssemblyRecorded)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated whether it acceptes scripture directives from the book assembly.");

    if (!m_AcceptTocDirectivesFromBookAssemblyRecorded)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated whether it acceptes TOC directives from the book assembly.");
  }


  /****************************************************************************/
  private boolean m_AcceptNotesDirectivesFromBookAssembly = false;
  private boolean m_AcceptNotesDirectivesFromBookAssemblyRecorded = false;

  private boolean m_AcceptPeripheralsDirectivesFromBookAssembly = false;
  private boolean m_AcceptPeripheralsDirectivesFromBookAssemblyRecorded = false;

  private boolean m_AcceptScriptureDirectivesFromBookAssembly = false;
  private boolean m_AcceptScriptureDirectivesFromBookAssemblyRecorded = false;

  private boolean m_AcceptTocDirectivesFromBookAssembly = false;
  private boolean m_AcceptTocDirectivesFromBookAssemblyRecorded = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                          CrossReferences                               **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputCrossReferencesDisposition (String optionList)
  {
    Config.instance().setOptionsOutputCrossReferencesDisposition(optionList);
    m_HadOptionListForOutputCrossReferencesDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputCrossReferencesDisposition (String dflt)
  {
    Config.instance().setDefaultOutputCrossReferencesDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputCrossReferencesDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputCrossReferencesDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputCrossReferencesDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputCrossReferencesDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputCrossReferencesDisposition()))
    {
      Config.instance().setOutputCrossReferencesDisposition(option);
      LogManager.instance().warning("Forcing outputCrossReferencesDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputCrossReferencesDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputCrossReferencesDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputCrossReferencesDisposition parameter value '" + parm + "'.  Supported options are " + options);
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputCrossReferencesDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                              Endnotes                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputEndnotesDisposition (String optionList)
  {
    Config.instance().setOptionsOutputEndnotesDisposition(optionList);
    m_HadOptionListForOutputEndnotesDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputEndnotesDisposition (String dflt)
  {
    Config.instance().setDefaultOutputEndnotesDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputEndnotesDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputEndnotesDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputNotesDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputEndnotesDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputEndnotesDisposition()))
    {
      Config.instance().setOutputEndnotesDisposition(option);
      LogManager.instance().warning("Forcing outputEndnotesDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputEndnotesDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputEndnotesDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputNotesDisposition parameter value '" + parm + "'.  Supported options are " + options);



    /**********************************************************************/
    /* Force the notes file name to something standard for those things
     * which require it.
     */

    Config.instance().setOutputEndnotesFileName(Config.instance().getIsoWithVariant() + "-Endnotes." + Config.instance().getOutputFileExtension());
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputEndnotesDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                             Footnotes                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputFootnotesDisposition (String optionList)
  {
    Config.instance().setOptionsOutputFootnotesDisposition(optionList);
    m_HadOptionListForOutputFootnotesDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputFootnotesDisposition (String dflt)
  {
    Config.instance().setDefaultOutputFootnotesDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputFootnotesDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputFootnotesDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputFootnotesDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputFootnotesDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputFootnotesDisposition()))
    {
      Config.instance().setOutputFootnotesDisposition(option);
      LogManager.instance().warning("Forcing outputFootnotesDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputFootnotesDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputFootnotesDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputNotesDisposition parameter value '" + parm + "'.  Supported options are " + options);



    /**********************************************************************/
    /* Force the notes file name to something standard for those things
     * which require it.
     */

    Config.instance().setOutputFootnotesFileName(Config.instance().getIsoWithVariant() + "-Footnotes." + Config.instance().getOutputFileExtension());
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputFootnotesDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Input                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/
  /****************************************************************************/
  /*
     Checks to see that the necessary input file parameters have been
     supplied. */

  private void validateInputParameters () throws ConverterException
  {
    if (0 == Config.instance().getInputFileExtension().length())
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "inputFileExtension not supplied.");

    if (0 == Config.instance().getInputFolderPath().length())
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "inputFolderPath not supplied.");
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                           Introduction                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputIntroductionDisposition (String optionList)
  {
    Config.instance().setOptionsOutputIntroductionDisposition(optionList);
    m_HadOptionListForOutputIntroductionDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputIntroductionDisposition (String dflt)
  {
    Config.instance().setDefaultOutputIntroductionDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputIntroductionDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputIntroductionDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputIntroductionDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputIntroductionDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputIntroductionDisposition()))
    {
      Config.instance().setOutputIntroductionDisposition(option);
      LogManager.instance().warning("Forcing outputIntroductionDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputIntroductionDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputIntroductionDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputIntroductionDisposition parameter value '" + parm + "'.  Supported options are " + options);
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputIntroductionDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                              Miscellaneous                             **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/
  /****************************************************************************/
  /*
      Checks to see that the necessary miscellaneous parameters have been
      supplied.  (It has been my intention to perform further validation
      here, and also to set up any parameters whose values could be deduced
      from the values of other parameters.  However, in fact no such processing
      currently appears to be necessary, so I have merely documented the
      actions of Config which have led me to that conclusion. */

  private void validateMiscellaneousParameters () throws ConverterException
  {
    // CAUTION: The comments below represent the situation as it stood at the
    // time I wrong this class.  I doubt that I shall have the enthusiasm
    // to keep them up to date, or that I shall rememeber to do so.
    // Where full paths to files or folders are required, all are subject to
    // standard processing.  If an absolute value is supplied, that is the
    // value used.  If a relative value is supplied, it is taken relative to
    // the folder which contains the job control file (the configuration file
    // whose name is passed to the converter on the command line).  If no value
    // is specified, we use the folder which contains the job control file.
    // At the time of writing, I don't think we have ever used relative naming;
    // and the only circumstances in which the user has failed to supply a
    // value have been those where no value is needed because we do not intend
    // to make use of the value.
    // bookAssemblyFileName is handled elsewhere in the present class.
    // bookAssemblyType is handled elsewhere in the present class.
    // chapterAndVerseSeparator, if missing, gives rise to an exception in Config.
    // converterUltimateTargetfileExtension defaults to pdb in Config.
    // copyright, if missing, gives rise to an exception in Config.
    // debugLevel defaults to zero in Config.
    // encoding defaults to UTF-8 in Config.
    // cssHref defaults to "" in Config.
    // dcTitle, if missing, gives rise to an exception in Config.
    // englishNameForVernacularLanguage is not used.
    // footnoteMarkerResetAt defaults to Book in Config.
    // footnoteMarkers defaults to Numeric in Config.
    // footnoteMarkerStyle defaults to Circulate in Config.
    // inputCssFolderPath uses standard path processing -- see above.
    // inputFileExtension, if missing, gives rise to an exception in Config.
    // inputFolderPath is handled elsewhere in the present class.
    // inputImageFolderPath uses standard path processing -- see above.
    // inputNotesFileName is used only by the proofer, not by any converters.
    // inputPeripheralMaterialFolderPath uses standard path processing -- see above.
    // inputTextFileName, if missing, gives rise to an exception in Config.
    // isoLanguageCode, if missing, gives rise to an exception in Config.
    // isoVariantLanguageCode defaults to "" in Config.
    // logFilePath uses standard path processing -- see above.
    // multiRefDifferentChapterSeparator, if missing, gives rise to an exception in Config.
    // multiRefSameChapterSeparator, if missing, gives rise to an exception in Config.
    // numberSystem, if missing, gives rise to an exception in Config.
    // orthography: Not used.
    // otmlId, if missing, gives rise to an exception in Config.
    // outputFileExtension, if missing, gives rise to an exception in Config.
    // outputFolderPath uses standard path processing -- see above.
    // outputIntroductionLocation is handled elsewhere in the present class.
    // outputNotes is handled elsewhere in the present class.
    // outputNotesFileName is handled elsewhere in the present class.
    // outputText is handled elsewhere in the present class.
    // outputTextFileName is handled elsewhere in the present class.
    // outputTocFileName defaults to "" in Config.
    // parserShouldParseScriptureReferences
    // postProcessorCommand defaults to nothing in Config.
    // prefixForVerseMarkersWhichAppearAtStartOfVerse
    // prefixForVerseMarkersWhichAppearAtEndOfVerse
    // prooferType: Used by proofer only.
    // rangeSeparator, if missing, gives rise to an exception in Config.
    // renderChapterNumber defaults to Yes in Config.
    // reportWarnings defaults to No in Config.
    // revision, if missing, gives rise to an exception in Config.
    // scriptureRevision: Not used.
    // scriptureStatus: Not used.
    // suffixForVerseMarkersWhichAppearAtStartOfVerse
    // suffixForVerseMarkersWhichAppearAtEndOfVerse
    // target, if missing, gives rise to an exception in Config.
    // textDir defaults to LTR in Config.
    // translationId, if missing, gives rise to an exception in Config.
    // useCoverImage defaults to Yes in Config.
    // validationLevel defaults to Strict in Config.
    // verseNumberLocation defaults to Begin in Config.
    // xxx*: Not used.
    // zipFilePath uses standard path processing -- see above.  No trailing separator is returned.
    // Term strings ...
    // actsIntroduction defaults to "" in Config.
    // alphabeticalContents defaults to "" in Config.
    // bibleIntroduction defaults to "" in Config.
    // chronology defaults to "" in Config.
    // coverTitle defaults to "" in Config.
    // deuterocanonIntroduction defaults to "" in Config.
    // endNotes defaults to "" in Config.
    // epistlesIntroduction defaults to "" in Config.
    // foreword defaults to "" in Config.
    // gospelsIntroduction defaults to "" in Config.
    // halfTitlePage defaults to "" in Config.
    // historyIntroduction defaults to "" in Config.
    // imprimatur defaults to "" in Config.
    // mapIndex defaults to "" in Config.
    // newTestamentIntroduction defaults to "" in Config.
    // oldTestamentTIntroduction defaults to "" in Config.
    // pentateuchIntroduction defaults to "" in Config.
    // poetryIntroduction defaults to "" in Config.
    // preface defaults to "" in Config.
    // promotional defaults to "" in Config.
    // prophecyIntroduction defaults to "" in Config.
    // publicationData defaults to "" in Config.
    // revelationIntroduction defaults to "" in Config.
    // tableOfAbbreviations defaults to "" in Config.
    // tableOfContents defaults to "" in Config.
    // titlePage defaults to "" in Config.
    // weightsMeasures defaults to "" in Config.
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Output                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/
  /****************************************************************************/
  /*
     Checks to see that the necessary output file parameters have been
     supplied.  Note that there's no point in checking whether the user
     has supplied an output file name, because via defaulting we'll always
     have one, even if none has been supplied. */

  private void validateOutputParameters () throws ConverterException
  {
    if (0 == Config.instance().getOutputFileExtension().length())
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "outputFileExtension not supplied.");
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                              Overall                                   **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputOverallDisposition (String optionList)
  {
    Config.instance().setOptionsOutputOverallDisposition(optionList);
    m_HadOptionListForOutputOverallDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputOverallDisposition (String dflt)
  {
    Config.instance().setDefaultOutputOverallDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputOverallDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputOverallDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputOverallDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputOverallDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputOverallDisposition()))
    {
      Config.instance().setOutputOverallDisposition(option);
      LogManager.instance().warning("Forcing outputOutputOverallDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputOverallDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputOverallDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputOutputOverallDisposition parameter value '" + parm + "'.  Supported options are " + options);



    /**********************************************************************/
    /* Force a standard name for those cases where we're sending all output
     * to a single file.
     */

    String s = Config.instance().getIsoWithVariant() + "." + Config.instance().getOutputFileExtension();
    Config.instance().setOutputOverallFileName(s);
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputOverallDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                            ScriptureText                               **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Sets the option list.

      @param optionList Options, separated by "¬".
   */

  public void setOptionListForOutputScriptureTextDisposition (String optionList)
  {
    Config.instance().setOptionsOutputScriptureTextDisposition(optionList);
    m_HadOptionListForOutputScriptureTextDisposition = true;
  }


  /****************************************************************************/
  /**
      Sets the default for this parameter.

      @param dflt Default.
   */

  public void setDefaultForOutputScriptureTextDisposition (String dflt)
  {
    Config.instance().setDefaultOutputScriptureTextDisposition(dflt);
  }


  /****************************************************************************/
  /*
    Checks that the options actually selected are mutually compatible, and
    carries out any other initial set-up which may be required.

     @throws ConverterException Parameters are invalid.
   */

  private void validateOutputScriptureTextDispositionParameters () throws ConverterException
  {
    /**********************************************************************/
    if (!m_HadOptionListForOutputScriptureTextDisposition)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what outputScriptureTextDisposition settings it supports.");



    /**********************************************************************/
    /* Force the configuration parameter if we support only one option. */

    String options = Config.instance().getOptionsOutputScriptureTextDisposition();
    String option = options.replaceAll("\\|", "");
    if (2 == options.length() - option.length() &&
        !options.equalsIgnoreCase(Config.instance().getOutputScriptureTextDisposition()))
    {
      Config.instance().setOutputScriptureTextDisposition(option);
      LogManager.instance().warning("Forcing outputScriptureTextDisposition to " + option + ".");
    }



    /**********************************************************************/
    /* Reject if we have an option which isn't in the option list. */

    String parm = Config.instance().getOutputScriptureTextDisposition();
    if (!Config.instance().validate(Config.instance().getNameOutputScriptureTextDisposition(), parm))
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Unsupported outputScriptureTextDisposition parameter value '" + parm + "'.  Supported options are " + options);



    /**********************************************************************/
    /* Force a standard name to be used in those situations where we have
     * one overall output.
     */

    String s = Config.instance().getIsoWithVariant() + "." + Config.instance().getOutputFileExtension();
    Config.instance().setOutputScriptureTextFileName(s);
  }


  /****************************************************************************/
  private boolean m_HadOptionListForOutputScriptureTextDisposition = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 TOC                                    **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
   * Returns an indication of whether or not we support TOC.
   *
   * @return True if we do.
   */

  public boolean getSupportsToc () { return m_SupportsToc; }


  /****************************************************************************/
  /**
   * Records the fact that we do or do note support TOC.
   *
   * @param yesNo If True, we support this setting.
   */

  public void setSupportsToc (boolean yesNo)
  {
    m_TocRecorded = true;
    m_SupportsToc = yesNo;
  }


  /****************************************************************************/
  /*
   *  Checks that the options actually selected are mutually compatible, and
   *  carries out any other initial set-up which may be required.  We force
   *  the output name to be index.html, which seems to be a standard for
   *  web files.  Note that some applications may not require it, but I
   *  don't think that matters.
   *
   *  @throws ConverterException Parameters are invalid.
   */

  private void validateTocParameters() throws ConverterException
  {
    if (!m_TocRecorded)
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "The converter has not indicated what TOC settings it supports.");
    Config.instance().setOutputTocFileName("index." + Config.instance().getOutputFileExtension());
  }


  /****************************************************************************/
  private boolean m_SupportsToc = false;
  private boolean m_TocRecorded = false;





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  private FeatureManager() {}


  /****************************************************************************/
  /* Singleton. */

  private static FeatureManager m_Instance = null;
}
